package top.hmtools;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ThreadPoolExecutor;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.sql.DataSource;

import org.apache.ibatis.jdbc.ScriptRunner;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;

import top.hmtools.autoConfiguration.DatasourceMybatisConfiguration;
import top.hmtools.autoConfiguration.RequestLoggerAutoConfiguration;
import top.hmtools.common.RequestLogBeanExtraQueue;
import top.hmtools.common.RequestLogBeanQueue;
import top.hmtools.common.StaticContent;
import top.hmtools.manager.IRequestLoggerManager;
import top.hmtools.manager.asynchronous.ThreadBoss;
import top.hmtools.manager.asynchronous.ThreadBossObserver;
import top.hmtools.service.IRequestLoggerService;

/**
 * 本工程的上下文
 * @author HyboJ
 *
 */
@Component
@ConditionalOnProperty(prefix=StaticContent.CONFIG_PREFIX,value=StaticContent.CONFIG_ITEM_KEY_NAME_ENABLED,matchIfMissing=true)
public class RLContext implements ApplicationContextAware,CommandLineRunner {
	
	private static Logger logger = LoggerFactory.getLogger(RLContext.class);
	
	/**
	 * spring 上下文
	 */
	private ApplicationContext applicationContext;
	
	/**
	 * 全局配置（自动配置，本程序入口）
	 */
	@Autowired
	public RequestLoggerAutoConfiguration requestLoggerAutoConfiguration;
	
	/**
	 * 数据源配置
	 */
	@Autowired
	private DatasourceMybatisConfiguration datasourceMybatisConfiguration;
	
	/**
	 * 用户请求日志记录管理者（将日志信息写入数据库的策略）
	 */
	@Autowired
	public IRequestLoggerManager requestLoggerManager;
	
	/**
	 * 用户请求日志记录写入数据库的服务者（执行数据写入到数据库）
	 */
	@Autowired
	public IRequestLoggerService requestLoggerService;
	
	/**
	 * 线程池
	 */
	@Autowired
	public ThreadPoolExecutor threadPoolExecutor; 
	
	/**
	 * 用户请求日志记录信息缓存队列（内存中）
	 */
	@Autowired
	public RequestLogBeanQueue requestLogBeanQueue;
	
	/**
	 * <b>追加用户请求日志记录扩展信息<b>缓存队列（内存中）
	 */
	@Autowired
	public RequestLogBeanExtraQueue requestLogBeanExtraQueue;
	
	/**
	 * mybatis的核心工厂
	 */
	private SqlSessionFactory sqlSessionFactory;
	
	/**
	 * 获取mybatis的核心 SqlSessionFactory
	 * @return
	 * @throws Exception
	 */
	private SqlSessionFactory sqlSessionFactoryBean() throws Exception {
	    DataSource dataSource = this.datasourceMybatisConfiguration.getDataSource();
	    if(dataSource == null){
	        return null;
	    }
		SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(dataSource);

		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
		sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:rl_mapper/*.xml"));
		SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
		return sqlSessionFactory;
	}
	
	/**
	 * TODO 这种写法会关闭不了 sqlsession
	 * 获取dao对象实例
	 * @param type
	 * @return
	 */
//	public <T>T getDao(Class<T> type){
//		T result = null;
//		if(this.sqlSessionFactory!=null){
//			SqlSession openSession = this.sqlSessionFactory.openSession();
//			result = openSession.getMapper(type);
//		}
//		return result;
//	}
	
	/**
	 * 获取mybatis操作数据库的连接会话对象实例
	 * @return
	 */
	public SqlSession getSqlSession() {
		SqlSession result=null;
		if (this.sqlSessionFactory != null) {
			result =  this.sqlSessionFactory.openSession();
		}
		return result;
	}
	
	/**
	 * 初始化所需要的表格、视图
	 * @param connection
	 */
	private void initDatabaseTables(Connection connection){
		try {
			ScriptRunner runner = new ScriptRunner(connection);  
			
//		    runner.setErrorLogWriter(System.console().writer());  
//		    runner.setLogWriter(System.console().writer());  //设置是否输出日志  
		    runner.setAutoCommit(true);//自动提交  
            runner.setFullLineDelimiter(false);  
            runner.setDelimiter(";");////每条命令间的分隔符  
            runner.setSendFullScript(false);  
            runner.setStopOnError(false);  
            
            //如果又多个sql文件，可以写多个runner.runScript(xxx), 
            InputStream sqlIS = this.getClass().getClassLoader().getResourceAsStream("sql/requestLogger_tables_views.sql");
            Reader reader = new InputStreamReader(sqlIS);
		    runner.runScript(reader);  
		    logger.info("初始化hm-tools-request-logger所需要的表格成功。。。");
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("初始化hm-tools-request-logger所需要的表格失败。。。"+e.getMessage(),e);
		}
	}
	
	/**
	 * 初始化存储过程、定时任务、数据库启用定时任务设置到数据库
	 * @param connection
	 */
	private void initPR_event(Connection connection){
		try {
			//设置要读取的sql文件（存储过程）
            InputStream sqlIS = this.getClass().getClassLoader().getResourceAsStream("sql/requestLogger_pr_all_event.sql");
            Reader reader = new InputStreamReader(sqlIS);
            StringBuilder script = new StringBuilder();
            BufferedReader lineReader = new BufferedReader(reader);
            
            //一行一行地读取sql文件内容
            String line;
            String delimiter = ";";
            while ((line = lineReader.readLine()) != null) {
            	//获取 SQL 语句断句符号（由“DELIMITER”命令配置，jdbc不直接支持此命令。）
            	if(line.contains("DELIMITER")){
            		delimiter = line.replace("DELIMITER", "").trim();
            		continue;
            	}
            	
            	//拼接SQL语句
              script.append(line);
              script.append(System.getProperty("line.separator", "\n"));
              
              //如果包含断句符号，则执行该段SQL语句
              if(line.trim().contains(delimiter)){
            	  String command = script.toString();
            	  Statement statement = connection.createStatement();
            	  logger.info("创建存储过程。。。");
            	  logger.info(command);
            	  statement.executeUpdate(command);
            	  script = new StringBuilder();
            	  logger.info("。。。成功");
            	  continue;
              }
            }

		    logger.info("初始化hm-tools-request-logger所需要的表格成功。。。");
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("初始化hm-tools-request-logger所需要的存储过程失败。。。"+e.getMessage(),e);
		}
	}
	
	/**
	 * spring 启动后初始化执行
	* 方法说明：                    postConstruct
	* 输入参数说明：           
	* 输出参数说明：           void
	*
	*
	 */
	@SuppressWarnings("unused")
	@PostConstruct
	private void postConstruct(){
		logger.info("spring启动后初始化执行。。。");		
		//初始化数据库表格
		Connection connection = null;
		try {
			connection = this.datasourceMybatisConfiguration.getDataSource().getConnection();
			this.initDatabaseTables(connection);
			this.initPR_event(connection);
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("初始化数据库表格失败："+e.getMessage(),e);
		}finally{
			if(connection!=null){
				try {
					connection.close();
				} catch (SQLException e) {
					e.printStackTrace();
					logger.error(e.getMessage(),e);
				}
			}
		}
		
		//初始化requestLoggerManager操作
	    try {
	    	if(this.requestLoggerManager != null){
	    		this.requestLoggerManager.init();
	    	}
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("初始化requestLoggerManager.init()失败："+e.getMessage(),e);
		}
        
	    //初始化守护线程
	    try {
	    	//初始化进行异步写入缓存到数据库的异步守护线程（是否执行写入操作，会读取配置值）
	    	ThreadBossObserver bossObserver = new ThreadBossObserver(this);
	    	
	    	//守护线程
	    	ThreadBoss threadBoss = new ThreadBoss(this);
	    	threadBoss.addObserver(bossObserver);//添加观察者
	    	Thread thread = new Thread(threadBoss);
	    	thread.setName("threadBoss");
	    	thread.setDaemon(true);
	    	thread.start();
		} catch (Exception e) {
			e.printStackTrace();
			logger.error("初始化进行异步写入缓存到数据库的异步守护线程失败："+e.getMessage(),e);
		}
	}
	
	/**
	 * spring正常关闭时执行。。。
	* 方法说明：                    preDestroy
	* 输入参数说明：           
	* 输出参数说明：           void
	*
	*
	 */
	@SuppressWarnings("unused")
	@PreDestroy
	private void preDestroy(){
	    logger.info("spring销毁前执行。。。");
	    //销毁requestLoggerManager前执行
	    if(this.requestLoggerManager != null){
            this.requestLoggerManager.destroy();
        }
	}
	
	/**
	 * 初始化
	 */
	private void init(){
		//初始化mybatis核心
		try {
			this.sqlSessionFactory = this.sqlSessionFactoryBean();
			logger.debug("初始化org.apache.ibatis.session.SqlSessionFactory成功");
		} catch (Exception e) {
			logger.error("初始化org.apache.ibatis.session.SqlSessionFactory失败");
			logger.error(e.getMessage(), e);
		}
		
		
	}

	/**
	 * 项目启动时运行
	 */
	@Override
	public void run(String... args) throws Exception {
		this.init();
	}

	/**
	 * 设置spring上下文
	 */
	@Override
	public void setApplicationContext(ApplicationContext applicationContext)
			throws BeansException {
		this.applicationContext = applicationContext;
	}

	/**
	 * 获取spring上下文
	 * @return
	 */
	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}
	
	

}
